package com.rookie.mvc;


import cn.hutool.log.StaticLog;
import cn.hutool.setting.dialect.Props;
import com.rookie.mvc.annotation.Action;
import com.rookie.mvc.annotation.AutoWired;
import com.rookie.mvc.annotation.Request;
import com.rookie.mvc.annotation.Service;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

//@WebServlet(urlPatterns = "/*")
public class DispatcherServlet extends HttpServlet{

    private static final long serialVersionUID = 1L;

    private static final String LOCATION = "contextConfigLocation";

    // 保存配置信息
    private Properties props = null;

    // 保存所有被扫描到的相关的类名
    private List<String> classNames = new ArrayList<String>();

    // ioc容器， 保存所有初始化的Bean
    private Map<String, Object> ioc = new HashMap<String, Object>();

    // 保存所有URL和方法的映射关系
    private Map<String, Method> handleMapping = new HashMap<String, Method>();

    public DispatcherServlet(){ super(); }

    @Override
    public void init(ServletConfig config) throws ServletException {
        doLoadConfig(config.getInitParameter(LOCATION));

        doScanner(props.getProperty("scanPackage"));

        doInstance();

        doAutowired();

        initHandleMapping();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doDispatch(req, resp);
    }

    /**
     * 读取配置文件
     * @param initParameter
     */
    private void doLoadConfig(String initParameter) {
        InputStream is = null;

        try {
            is = this.getClass().getClassLoader().getResourceAsStream("application.properties");
            if (is != null) {
                props = new Properties();
                props.load(is);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != is) {
                    return;
                }
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 递归扫描所有的Class文件
     * @param packageName
     */
    private void doScanner(String packageName){
        // 将所有的包路径转换为文件路径
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".", "/"));
        File dir = new File(url.getFile());
        if (!dir.exists()) {return;}
        for (File file : dir.listFiles()){
            // 如果是文件夹，继续递归
            if (file.isDirectory()) {
                doScanner(packageName + "." + file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replace(".class", "").trim());
            }
        }
    }

    /**
     * 首字母小写
     * @param str
     * @return
     */
    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 初始化所有的类，保存到IOC容器中
     */
    private void doInstance(){
        if (classNames.size() == 0) {
            return;
        }
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Action.class)) {
                    // 类名首字母转小写，实例化后保存到IOC容器
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service service = clazz.getAnnotation(Service.class);
                    String beanName =  service.value();
                    // 定义了名字就用定义的名字
                    if (!"".equals(beanName.trim())){
                        ioc.put(beanName, clazz.newInstance());
                        continue;
                    }
                    // 没定义名字，按接口类型创建一个实例
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(anInterface.getName(), clazz.getInterfaces());
                    }
                } else {
                    continue;
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对IOC容器中的类进行初始化
     */
    private void doAutowired(){
        if (ioc.isEmpty()){
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 拿到实例对象中的所有属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(AutoWired.class)) {
                    continue;
                }

                AutoWired autoWired = field.getAnnotation(AutoWired.class);
                String beanName = autoWired.value().trim();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }
                // 设置私有属性的访问权限
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    /**
     * 初始化映射链
     */
    private void initHandleMapping(){
        if (ioc.isEmpty()){return;}

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Action.class)) {
                continue;
            }

            String baseUrl = "";
            // 获取Action的配置
            if (clazz.isAnnotationPresent(Action.class)) {
                Request request = clazz.getAnnotation(Request.class);
                baseUrl = request.value();
            }

            // 获取Method的配置
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(Request.class)) { continue; }

                // 映射URL
                Request request = method.getAnnotation(Request.class);
                String url = ("/" + baseUrl + "/" + request.value()).replaceAll("/+", "/");
                handleMapping.put(url, method);
                StaticLog.info("mapped: " + url + "[" + method +"]");
            }

        }
    }

    /**
     * 获取请求并进行反射
     */
    private void doDispatch(HttpServletRequest request, HttpServletResponse response){
        if (handleMapping.isEmpty()){return;}

        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");

        if (!handleMapping.containsKey(url)) {
            try {
                response.getWriter().write("404 Not Found");
                response.setStatus(404);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        Map<String, String[]> params = request.getParameterMap();
        Method method = handleMapping.get(url);
        // 获取方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 获取请求的参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        // 保存参数值
        Object[] paramValues = new Object[parameterTypes.length];
        // 遍历方法的参数列表
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            // 特殊参数处理
            if (parameterType == HttpServletRequest.class) {
                paramValues[i] = request;
            } else if (parameterType == HttpServletResponse.class) {
                paramValues[i] = response;
            } else if (parameterType == HttpSession.class) {
                paramValues[i] = request.getSession();
            } else {
                // 获取值
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String value = Arrays.toString(entry.getValue())
                            .replaceAll("\\[|\\]", "")
                            .replaceAll(",\\s", "");
                    paramValues[i] = value;
                }
            }
        }

        // 反射
        try {
            String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
            method.invoke(ioc.get(beanName), paramValues);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

}